package cn.airfei.aircore.core.aspect;

import cn.airfei.aircore.core.exception.AppException;
import cn.airfei.aircore.core.utils.RedisUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.digest.DigestUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.util.Arrays;
import java.util.Map;

/**
 * @description: 通过redis 简单校验重复请求 ,在相关方法上添加  @CheckRepeatRequest 注解即可
 * @author: air
 * @create: 2021-01-21 14:58
 */
@Aspect
@Component
@Slf4j
public class CheckRepeatRequestAspect {
    private final static Integer CODE = 722000;

    private final static String ERROR_MSG = "您请求过于频繁,系统忙不过来了,请稍后再试试";

    private final static String ATTRIBUTE_KEY = "air_core_redis_key_md5";

    @Resource
    private RedisUtil redisUtil;

    @Resource
    private HttpServletRequest httpServletRequest;

    @Pointcut("@annotation(cn.airfei.aircore.core.annotations.CheckRepeatRequest)")
    public void annotationPointcut() {

    }

    @Before(value = "annotationPointcut()")
    public void before(JoinPoint jp) {
        String keyMd5 = setRedisKeyMd5(jp);
        if (redisUtil.get(keyMd5) != null || !redisUtil.setIfAbsent(keyMd5, "true", 600L)) {
            log.warn("成功拦截重复请求,url:{},method:{},ip:{}", httpServletRequest.getRequestURI(), httpServletRequest.getMethod(), httpServletRequest.getRemoteAddr());
            throw new AppException(CODE, ERROR_MSG);
        }
    }

    @AfterReturning(value = "annotationPointcut()", returning = "result")
    public void afterReturning(JoinPoint jp, Object result) {
        String keyMd5 = (String) httpServletRequest.getAttribute(ATTRIBUTE_KEY);
        redisUtil.remove(keyMd5);
    }

    @AfterThrowing(value = "annotationPointcut()", throwing = "e")
    public void afterThrowing(JoinPoint jp, Exception e) {
        String keyMd5 = (String) httpServletRequest.getAttribute(ATTRIBUTE_KEY);
        // 除拦截重复请求异常外,其余全部异常,则删除kayMd5
        if (e instanceof AppException) {
            AppException appException = (AppException) e;
            if (!appException.getCode().equals(CODE)) {
                redisUtil.remove(keyMd5);
            }
        } else {
            redisUtil.remove(keyMd5);
        }
    }

    /**
     * 设置redis key
     * key:所有参数+url+method+ip的MD5值
     *
     * @param jp
     * @return
     */
    private String setRedisKeyMd5(JoinPoint jp) {
        // 存储参数等
        StringBuilder tempStr = new StringBuilder();
        tempStr.append(httpServletRequest.getRequestURI());
        tempStr.append(httpServletRequest.getMethod());
        tempStr.append(httpServletRequest.getRemoteAddr());
        Map<String, String[]> parameterMap = httpServletRequest.getParameterMap();
        for (String key : parameterMap.keySet()) {
            String[] values = parameterMap.get(key);
            if (values != null && values.length > 0) {
                for (String value : values) {
                    tempStr.append(value);
                }
            }
        }
        tempStr.append(Arrays.toString(jp.getArgs()));

        String md5Hex = DigestUtils.md5Hex(String.valueOf(tempStr));
        httpServletRequest.setAttribute(ATTRIBUTE_KEY, md5Hex);
        return md5Hex;
    }

}
